package org.hsqldb;

import java.io.IOException;

import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.index.NodeLinkList;
import org.hsqldb.index.NodeLinkListDisk;
import org.hsqldb.lib.IntLookup;
import org.hsqldb.lib.java.JavaSystem;
import org.hsqldb.persist.PersistentStore;
import org.hsqldb.persist.RowStoreLinkList;
import org.hsqldb.rowio.RowInputInterface;
import org.hsqldb.rowio.RowOutputInterface;
public class RowLinkList extends Row{
	public NodeLinkList nPrimaryNode;
	public static final int NO_POS = -1;

    //
    int     storageSize;
    int     keepCount;
    boolean isInMemory;
    int     accessCount;

    /**
     *  Flag indicating unwritten data.
     */
    private boolean hasDataChanged;

    /**
     *  Flag indicating Node data has changed.
     */
    private boolean hasNodesChanged;
    
    
	RowLinkList(TableBase table, Object[] data) {
		super(table, data);
	}
	
	public RowLinkList(TableBase table, Object[] data,int position,
            PersistentStore store) {
		super(table, data);
        this.position = position;

        setNewNodes(store);
		hasNodesChanged = hasDataChanged = true;
	}
	
	public RowLinkList(TableBase table, Object[] object,
			RowStoreLinkList rowStoreLinkList) {
		super(table, object);

        setNewNodes(rowStoreLinkList);

        hasDataChanged = hasNodesChanged = true;
	}
	 public RowLinkList(TableBase table, RowInputInterface in) throws IOException{
	    	super(table, null);

	        position    = in.getPos();
	        storageSize = in.getSize();

	        int indexcount = table.getIndexCount();

	        nPrimaryNode = new NodeLinkListDisk(this, in, 0);

	        NodeLinkList n = nPrimaryNode;

	        for (int i = 1; i < indexcount; i++) {
	            n.nodeNextIndex = new NodeLinkListDisk(this, in, i);
	            n       = n.nodeNextIndex;
	        }

	        rowData = in.readData(table.getColumnTypes());
		}
	 
	@Override
	public synchronized boolean hasChanged(){
		return hasDataChanged || hasNodesChanged;
	}
	
	
	public void updateAccessCount(int count) {
        accessCount = count;
    }

    public int getAccessCount() {
        return accessCount;
    }

    public int getStorageSize() {
        return storageSize;
    }

    public boolean isMemory() {
        return false;
    }
	 /**
     * Sets flag for row data change.
     */
    public synchronized void setChanged(boolean changed) {
        hasDataChanged = changed;
    }
    /**
     * Sets flag for Node data change.
     */
    public synchronized void setNodesChanged() {
        hasNodesChanged = true;
    }
   

	
	@Override
	public void setPos(int p){
		position = p;
		NodeLinkList primaryNode = this.nPrimaryNode;
		while(primaryNode != null){
			((NodeLinkListDisk)primaryNode).setPos(p);
			primaryNode = primaryNode.nodeNextIndex;
		}
	}
	/**
     * Returns the Table to which this Row belongs.
     *
     * @return Table
     */
    public TableBase getTable() {
        return table;
    }

    public void setStorageSize(int size) {
        storageSize = size;
    }

    /**
     * Returns true if any of the Nodes for this row is a root node.
     * Used only in Cache.java to avoid removing the row from the cache.
     *
     * @return boolean
     */
    public synchronized boolean isKeepInMemory() {
        return keepCount > 0;
    }

   public void delete(PersistentStore store) {

       RowLinkList row = this;

       if (!row.keepInMemory(true)) {
           row = (RowLinkList) store.get(row, true);
       }

       NodeLinkList n = nPrimaryNode;

       while (n != null) {
           n.delete();

           n = n.nodeNextIndex;
       }
       row.keepInMemory(false);
   }


   public synchronized boolean keepInMemory(boolean keep) {

       if (!isInMemory) {
           return false;
       }

       if (keep) {
           keepCount++;
       } else {
           keepCount--;

           if (keepCount < 0) {
               throw Error.runtimeError(ErrorCode.U_S0500,
                                        "RowAVLDisk - keep count");
           }
       }

       return true;
   }

   public synchronized boolean isInMemory() {
       return isInMemory;
   }

   public synchronized void setInMemory(boolean in) {

       isInMemory = in;

       if (in) {
           return;
       }

       NodeLinkList n = nPrimaryNode;

       while (n != null) {
           n.setInMemory(in);

           n = n.nodeNextIndex;
       }
   }
	public void setNewNodes(PersistentStore store) {

        int indexCount = store.getAccessorKeys().length;

        nPrimaryNode = new NodeLinkListDisk(this,0);

        NodeLinkListDisk n = (NodeLinkListDisk) nPrimaryNode;
        for (int i = 1; i < indexCount; i++) {
            n.nodeNextIndex = new NodeLinkListDisk(this,i);
            n       = (NodeLinkListDisk) n.nodeNextIndex;
        }
    }
	public int getRealSize(RowOutputInterface out) {
        return out.getSize(this);
    }
	
	/**
     * Lifetime scope of this method depends on the operations performed on
     * any cached tables since this row or the parameter were constructed.
     * If only deletes or only inserts have been performed, this method
     * remains valid. Otherwise it can return invalid results.
     *
     * @param obj row to compare
     * @return boolean
     */
    public boolean equals(Object obj) {

        if (obj == this) {
            return true;
        }

        if (obj instanceof RowLinkList) {
            return ((RowLinkList) obj).position == position;
        }

        return false;
    }

    /**
     * Hash code is valid only until a modification to the cache
     *
     * @return file position of row
     */
    public int hashCode() {
        return position;
    }
    /**
     * Returns the Node for a given Index, using the ordinal position of the
     * Index within the Table Object.
     */
    public NodeLinkList getNode(int index) {

        NodeLinkList n = nPrimaryNode;

        while (index-- > 0) {
            n = n.nodeNextIndex;
        }

        return n;
    }
    
    /**
     *  Returns the Node for the next Index on this database row, given the
     *  Node for any Index.
     */
    NodeLinkList getNextNode(NodeLinkList n) {

        if (n == null) {
            n = nPrimaryNode;
        } else {
            n = n.nodeNextIndex;
        }

        return n;
    }
    
    public NodeLinkList insertNode(int index) {

        NodeLinkList backnode = getNode(index - 1);
        NodeLinkList newnode  = new NodeLinkListDisk(this,index);

        newnode.nodeNextIndex  = backnode.nodeNextIndex;
        backnode.nodeNextIndex = newnode;

        return newnode;
    }
    public void clearNonPrimaryNodes() {

        NodeLinkList n = nPrimaryNode.nodeNextIndex;

        while (n != null) {
            n.delete();

            n          = n.nodeNextIndex;
        }
    }
    
    
    public void restore() {}
    
    /**
     * Helps GC, removing secondary node links.
     */
    public void destroy() {

        JavaSystem.memoryRecords++;

        clearNonPrimaryNodes();

        NodeLinkList n = nPrimaryNode;

        while (n != null) {
            NodeLinkList last = n;

            n          = n.nodeNextIndex;
            last.nodeNextIndex = null;
        }
    }

	public void setData(Object[] data) {
        this.rowData = data;
    }
	 /**
     * Used exclusively by Cache to save the row to disk. New implementation in
     * 1.7.2 writes out only the Node data if the table row data has not
     * changed. This situation accounts for the majority of invocations as for
     * each row deleted or inserted, the Nodes for several other rows will
     * change.
     */
    public void write(RowOutputInterface out) {

        try {
            writeNodes(out);

            if (hasDataChanged) {
                out.writeData(this, table.colTypes);
                out.writeEnd();

                hasDataChanged = false;
            }
        } catch (IOException e) {}
    }

    
    public void write(RowOutputInterface out, IntLookup lookup) {

        out.writeSize(storageSize);

        NodeLinkList rownode = nPrimaryNode;

        while (rownode != null) {
            ((NodeLinkListDisk) rownode).write(out, lookup);

            rownode = rownode.nodeNextIndex;
        }

        out.writeData(this, table.colTypes);
        out.writeEnd();
    }
	/**
     *  Writes the Nodes, immediately after the row size.
     *
     * @param out
     *
     * @throws IOException
     */
    private void writeNodes(RowOutputInterface out) throws IOException {

        out.writeSize(storageSize);

        NodeLinkList n = nPrimaryNode;

        while (n != null) {
            n.write(out);

            n = n.nodeNextIndex;
        }

        hasNodesChanged = false;
    }


}
